package com.xpec.c4.service.module;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.infinispan.util.concurrent.ConcurrentHashSet;

import com.ms.game.common.utility.xxx;
import com.ms.service.db.jedis.JedisManager;
import com.xpec.c4.codec.CodecUtility;
import com.xpec.c4.db.log.LogJobExecutor;
import com.xpec.c4.db.log.SaveLogTask;
import com.xpec.c4.game.common.AutoGC;
import com.xpec.c4.game.cronjob.CronJobManager;
import com.xpec.c4.game.system.InitData;
import com.xpec.c4.game.system.ServerMain;
import com.xpec.c4.game.time.JobExecutor;
import com.xpec.c4.game.utility.GUIDFactory;
import com.xpec.c4.game.utility.GameUtility;
import com.xpec.c4.message.MessageTypes;
import com.xpec.c4.service.common.MessageHandle;
import com.xpec.c4.service.contexts.Contexts;
import com.xpec.c4.service.group.ServerGroup;
import com.xpec.c4.service.group.transportlayer.GroupMessage;
import com.xpec.c4.service.group.transportlayer.TransportManager;
//import com.xpec.c4.service.telnet.TelnetServer;
import com.xpec.c4.service.message.MessageAction;
import com.xpec.c4.service.module.message.ModuleStatus;


/**
 * 
 * 
 * @author lipeilin
 * @2011-3-9 下午06:28:39
 * 
 */
public class ModuleServer {

	/**
	 * 
	 */
	private static final Log log = LogFactory.getLog(ModuleServer.class);
	
	/**
	 * 
	 */
	public static final String ALL_MODULE_START_UP = "ALL_MODULE_START_UP";

	/**
	 * 模块列表
	 * 因為同步性問題先改成用Concurrent版
	 */
	private Set<BasicServiceModule> modules = new ConcurrentHashSet<BasicServiceModule>();

	/**
	 * 模块列表，用hashmap方便查找
	 * 因為同步性問題先改成用Concurrent版
	 */
	private Map<String, BasicServiceModule> moduleMaps = new ConcurrentHashMap<String, BasicServiceModule>();

	/**
	 * 模块名称列表
	 * 因為同步性問題先改成用Concurrent版
	 */
	private Set<String> moduleNames = new ConcurrentHashSet<String>();

	/**
	 * 所有模块，暂时没用
	 * 因為同步性問題先改成用Concurrent版
	 */
	private Set<BasicServiceModule> allModules = new ConcurrentHashSet<BasicServiceModule>();

	/**
	 * 服务器名称
	 */
	private String name = "NONE";

	/**
	 * 
	 */
	private static ModuleServer instance = null;
	
	/**
	 * 是否在加载数据
	 */
	private boolean loadData = true;

	/**
	 * 是否已经完成全部启动装载
	 */
	private volatile boolean ready = false;
	
	/**
	 * 提供project程式碼裡有mobile的地方做個開關.如果該版本沒啟用mobile module.則相關mobile程式碼應該關掉
	 */
	public static boolean MOBILE_ENABLED = false;
	
	/**
	 * 
	 */
	private ModuleServer() {
	}

	/**
	 * 
	 * @return
	 */
	public static ModuleServer getInstance() {
		if (instance == null) {
			instance = new ModuleServer(ServerProperties.localServer);
		}
		return instance;
	}

	/**
	 * 
	 * @param name
	 */
	private ModuleServer(String name) {
		
		if(name == null){
			log.warn("server name is null!");
			return;
		}
		
		this.name = name;

		// 全部模块启动
		if (name.equals(ALL_MODULE_START_UP)) {
			Collection<List<String>> allServer = ServerProperties
					.getModules().values();
			for (List<String> list : allServer) {
				// 先檢查一遍底下所有的 module name, 只要有屬於 account 的就不在這邊啟動
				xxx.nothing();
				boolean foundAccountModule = false;
				for (String mn : list) {
					if (ServerProperties.isTargetModule(mn, ModuleName.ACCOUNT)) {
						foundAccountModule = true;
						break;
					}
				}

				if (foundAccountModule) {
					continue;
				}

				for (String mn : list) 
				{
					addModule(createModule(mn));
				}
			}
		}
		// 启动指定的模块
		else {
			List<String> modulesName = ServerProperties.getModules().get(name);
			if( modulesName != null )
			{
				for (String mn : modulesName) 
				{
					//account不需載入  屬於web
					//if( mn.equals("account") ){
						//loadData = false;
					//}
					addModule(createModule(mn));		
				}
			}else{
				log.warn(name + " modules not found!");
			}
		}
		GUIDFactory.setProcessId(this.name);
		MessageHandle.setModuleServer(this);

	}

	/**
	 * 通过模块名称，创建模块
	 * 
	 * @param moduleName
	 * @return
	 * 
	 * @author lipeilin
	 * @2011-3-10 下午12:38:42
	 */
	public BasicServiceModule createModule(String moduleName) {
		String[] name = moduleName.split("_");
		// String className = GroupProperties.moduleClassMapping.get(name[0]);
		String className = ModuleClassMapping.getClassName(name[0]);
		
		if(className == null){
			log.error("Module class not defined(" + moduleName + ")",
					new NullPointerException());
			return null;
		}

		BasicServiceModule module = null;
		try {
			module = (BasicServiceModule) Class.forName(className)
					.newInstance();

			module.setModuleName(moduleName);

			// 后面作为参数置入
			if (name.length > 1) {
				module.setArguments(Arrays.copyOfRange(name, 1, name.length));
			}
		} catch (InstantiationException e) {
			log.error("cannot load module class!", e);
		} catch (IllegalAccessException e) {
			log.error("cannot load module class!", e);
		} catch (ClassNotFoundException e) {
			log.error("cannot load module class!", e);
		}
		return module;
	}
	
	/**
	 * 啟動指定的module
	 * @param module
	 */
	public void startModule(String moduleName){
		if(!moduleNames.contains(moduleName)){
			moduleNames.add(moduleName);
			BasicServiceModule module = createModule(moduleName); 
			addModule(module);
			// startup
			module.init();
			module.startup();
		}
	}

	/**
	 * 結束指定的module
	 * @param module
	 */
	public void endModule(String moduleName){
		BasicServiceModule module = moduleMaps.get(moduleName);
		if(module != null){
			module.shutdown();
			moduleNames.remove(moduleName);
			removeModule(module);			
		}
	}
	
	/**
	 * Not Yet~
	 * 終結指定的module
	 * @param moduleName
	 */
	public void killModule(String moduleName){
		// Not Yet~
		endModule(moduleName);
	}

	/**
	 * 
	 * @throws IOException
	 */
	public void start(boolean doAuxilaryWork) throws IOException{
		//[1] register codec info
		try {
			CodecUtility.CODEC_TYPE_DEFAULT = (byte) ServerProperties.getInt("default_codec",CodecUtility.CODEC_TYPE_H2O);
			CodecUtility.registerH2oClass();
			CodecUtility.registerH2oMessageType();
			CodecUtility.registerAMF3MessageType();
			CodecUtility.registerMarshallingMessageType();
		} catch (Exception e1) {
			e1.printStackTrace();
			throw new RuntimeException(e1.getMessage());
		}
		
		// initial cron job manager
		CronJobManager.initial();
		
		//[2] start jgroup transport layer
		try {
			ServerGroup.getTransportManager().startGroupTransport();
		} catch (Exception e) {
			log.error("cannot start up Group !", e);
			throw new RuntimeException("cannot start up Group !", e);
		}
		moduleStatMap.put("jgroups",new TreeMap<String,Integer>());
		moduleRateMap.put("jgroups",new TreeMap<String,Integer>());
		
		//[3] load static data
		if(loadData){
			try {
				InitData.initAll();
				ConsistentGroupName.initial();
				
				// 註冊放玩家資料的 redis resource name group
				Set<String> node = JedisManager.getJedisPlayerDatabasesMap().keySet();
				ConsistentGroupName.registerCustomConsistentGroupName(ModuleName.GROUP_PLAYER_DATA_REDIS, node);
			} catch (Exception e1) {
				log.error("load data is error!", e1);
				throw new RuntimeException("load data is error!", e1);
			}
		}
		
		//[4] init server group info
//		try {
//			ServerGroup.getDataManager();
//		} catch (Exception e) {
//			log.error("cannot start up Group !", e);
//			throw new RuntimeException("cannot start up Group !", e);
//		}
		
		//[5] start each module
		 
		for (BasicServiceModule module : modules) {
			module.init();
			module.startup();
			moduleNames.add(module.getModuleName());
			
			// 
//			if(module instanceof DataCenterModule) {
//				((DataCenterModule) module).registerJmxInfo();
//			}
			
//			if(module instanceof LoginModule) {
//				((LoginModule) module).registerJmxInfo();
//			}
			
//			if(module instanceof GatewayModule) {
//				((GatewayModule) module).registerJmxInfo();
//			}
			
			log.info("*** module " + module.getModuleName() + " is loaded!!! ***");
			moduleStatMap.put(module.getModuleName(),new TreeMap<String,Integer>());
			moduleRateMap.put(module.getModuleName(),new TreeMap<String,Integer>());
		}
		
		//[6]auxilary work as scheduler job
		if(doAuxilaryWork)
		{
			try
			{
				auxilaryInit();
			}
			catch (Exception e) 
			{
				log.error("cannot auxilaryInit!", e);
				throw new RuntimeException("cannot auxilaryInit!", e);
			}
		}
		
		//[6.5] server ready to serve now!
		this.ready = true;
		
		//[7]broadcast my module info
		ServerGroup.getTransportManager().sendLocalMemberInfo();
		//[7.4]notice all module module Start
		for(BasicServiceModule module : modules)
		{
			module.onModuleServerReadyStart();
		}
		//[7.5]auxilary work after
		if(doAuxilaryWork)
		{
			try
			{
				auxilaryAfterReady();
			}
			catch (Exception e) 
			{
				log.error("cannot auxilaryAfterReady", e);
				throw new RuntimeException("cannot auxilaryAfterReady!", e);
			}
		}
//		//[8]open telnet management server
//		String portString = System.getProperty("com.xpec.c4.telnetPort");
//		
//		if(portString != null)
//		{
//			new TelnetServer(Integer.parseInt(portString)).run();
//		}
		
		//[9]open timer thread for calcStat
		Timer timer = new Timer();
		timer.scheduleAtFixedRate(new CalcTask(this), 1000, 5000);
		
		ServerGroup.getTransportManager().notifyServerReady();
		
//		//[10]
//		this.registerJmxInfo();
	}

	public boolean isReady() {
		return ready;
	}

	private void auxilaryInit() {
//		Contexts.addContextsListener(new ContextsStatusListener());

		JobExecutor.scheduleAtFixedRate(new AutoGC(), 5, 5, TimeUnit.MINUTES);
	
		// 目前沒在使用,先remark 2013/5/17
		//DB
//		JobExecutor.scheduleAtFixedRate(new PlayerIdleRemover(), FilterConstants.PLAYER_SAVE_DELAY, FilterConstants.SOCIAL_IDLE_TIME, TimeUnit.MILLISECONDS );
//		JobExecutor.scheduleAtFixedRate(new SocialIdleRemover(), FilterConstants.PLAYER_SAVE_DELAY, FilterConstants.SOCIAL_IDLE_TIME, TimeUnit.MILLISECONDS );
//		JobExecutor.scheduleAtFixedRate(new AccountIdleRemover(), FilterConstants.PLAYER_SAVE_DELAY, FilterConstants.SOCIAL_IDLE_TIME, TimeUnit.MILLISECONDS);
		// end of 目前沒在使用,先remark
		
		//定時存储log
		LogJobExecutor.scheduleAtFixedRate(new SaveLogTask(), 0, 60000, TimeUnit.MILLISECONDS);
		
		//GM
//		//加载禁言玩家列表
//		BanChattingPlayerDAO.loadAll();
//		
//		//加载所有的黑名单账户
//		BlackListDAO.loadAll();
		
		//初始化掉宝率统计相关
		// DropInfoStatisticCounter.initAllCounter();
		
		//暂时定掉宝率统计数据半小时存储一次
		// JobExecutor.scheduleAtFixedRate(new DropPolicyStatisticSaveTask(),60000,45*TimeConstants.MILLI_SECOND_PRE_MINUTE , TimeUnit.MILLISECONDS);
		//End of GM
		
		
		//预处理应该删除或修改的列，以后正式版的时候该代码删除
		ServerMain.modifyColumn();		
		
	}
	
	/**
	 * 伺服器啟動後才要執行的
	 */
	private void auxilaryAfterReady() {
//		//请求同步公会数据
//		GuildMessageSender.sendRequestAllGuildData();
//		
//		// 初始化时空之轮数据
//		// 屏蔽
//		// ChronoWarManager.getInstance().initChronoWarData();
	}
	
	/**
	 *	停止服务器 
	 */
	public void stop() {
		
		ArrayList<BasicServiceModule> allModules = new ArrayList<BasicServiceModule>(modules);
		
		//[1]stop each module
		for (BasicServiceModule module : allModules) {

			try {
			    module.shutdown();
			    while(false == module.isTerminated())
			    {
			    	Thread.sleep(100);
			    }
			} catch (Exception e) {
				log.error("stop server module error!" ,e);
			}
			moduleNames.remove(module.getModuleName());
			log.info("*** module " + module.getModuleName() + " is stop!!! ***");
		}

		//[2]stop dataManager
//		try {
//			ServerGroup.getDataManager().stop();
//		} catch (Exception e) {
//			log.error("stop server transport error!" ,e);
//		}
		
		//[3]stop jgroup
		try {
			ServerGroup.getTransportManager().stopGroupTransport();
		} catch (Exception e) {
			log.error("stop server transport error!" ,e);
		}
	}

	/**
	 * 添加模块
	 * 
	 * @param module
	 * 
	 * @author lipeilin
	 * @2011-3-9 下午06:33:38
	 */
	public void addModule(BasicServiceModule module) {
		modules.add(module);
		allModules.add(module);
		moduleMaps.put(module.getModuleName(), module);
	}

	/**
	 * 移除模块
	 * 
	 * @param module
	 * @return
	 * 
	 * @author lipeilin
	 * @2011-3-9 下午06:33:46
	 */
	public boolean removeModule(BasicServiceModule module) {
		allModules.remove(module);
		moduleMaps.remove(module.getModuleName());
		boolean result = modules.remove(module);

		if (log.isInfoEnabled()) {
			log.info("module is remove! " + module.getModuleName());
		}

		return result;
	}
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public void startGroupTransport() throws Exception {
		//   

	}

	public Set<BasicServiceModule> getModules() {
		return modules;
	}

	public Set<BasicServiceModule> getAllModules() {
		return allModules;
	}

	public Set<String> getModuleNames() {
		return moduleNames;
	}

	public void setModuleNames(HashSet<String> moduleNames) {
		this.moduleNames = moduleNames;
	}
	
	/**
	 * 
	 * @param key
	 * @return
	 */
	public BasicServiceModule getBasicServiceModule(String key){
		return (moduleMaps != null) ? moduleMaps.get(key):null;
	}

	/**
	 * create a tmp file like serverXY.on/.off to inform bash script
	 * @param startOrStop start or stop server
	 */
	public void createSignalFile(boolean startOrStop) 
	{
		File tmpDir = new File("./tmp/");
		
		if (!tmpDir.exists())
		{
			tmpDir.mkdir();  
		}		
				
		File tmpFile = new File("./tmp/"
								+ getName()
								+ ((startOrStop == true) ? ".on" : ".off"));
		try {
			tmpFile.createNewFile();
		} catch (IOException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}

	public void createServerReadySignalFile() 
	{
		File tmpDir = new File("./tmp/");
		
		if (!tmpDir.exists())
		{
			tmpDir.mkdir();  
		}		
				
		File tmpFile = new File("./tmp/" + "ServerConnectReady.on");
		
		try {
			tmpFile.createNewFile();
		} catch (IOException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}

	private volatile String cachedStat = new String();
	
	public String getStat()
	{
		return cachedStat;
	}
	
	/**
	 * 状态监视用
	 */
	private void formatStatString() 
	{
		try {
			StringBuffer sb = new StringBuffer();

			ArrayList<BasicServiceModule> allModules = new ArrayList<BasicServiceModule>(
					modules);
			// each module stat
			for (BasicServiceModule module : allModules) {
				sb.append(module.getModuleName() + ":\r\n");
				Map<String, Integer> rateMap = (Map<String, Integer>) moduleRateMap
						.get(module.getModuleName());

				if (rateMap != null) {

					for (String key : rateMap.keySet()) {
						Integer v = rateMap.get(key);
						sb.append("\t" + key + ":" + v + "\r\n");
					}
				}
			}

			// jgroup stat
			sb.append("jgroup :\r\n");
			Map<String, Integer> rateMap = (Map<String, Integer>) moduleRateMap
					.get("jgroups");
			if (rateMap != null) {
				for (String key : rateMap.keySet()) {
					Integer v = rateMap.get(key);
					sb.append("\t" + key + ":" + v + "\r\n");
				}
			}
			cachedStat = sb.toString();
		} catch (Exception e) {
			
		}
	}
	
	/**
	 * get stat of each module
	 * @return
	 */
	private Map<String,Object> moduleStatMap = new HashMap<String,Object>();
	private Map<String,Object> moduleRateMap = new HashMap<String,Object>();
	
	private void calcStat(long passedTime) 
	{		
		ArrayList<BasicServiceModule> allModules = new ArrayList<BasicServiceModule>(modules);
		//each module stat
		for (BasicServiceModule module : allModules)
		{
			Map<String,Integer> nowMap = module.getStatistics();
			Map<String,Integer> oldMap = (Map<String,Integer>)moduleStatMap.get(module.getModuleName());
			Map<String,Integer> rateMap = (Map<String,Integer>)moduleRateMap.get(module.getModuleName());
			
			calcRate(passedTime, nowMap, oldMap, rateMap);
			moduleStatMap.put(module.getModuleName(), nowMap);
		}
		
		//jgroup stat
		Map<String,Integer> nowMap = ServerGroup.getTransportManager().getStat();
		Map<String,Integer> oldMap = (Map<String,Integer>)moduleStatMap.get("jgroups");
		Map<String,Integer> rateMap = (Map<String,Integer>)moduleRateMap.get("jgroups");
					
		calcRate(passedTime, nowMap, oldMap, rateMap);			
		moduleStatMap.put("jgroups", nowMap);
	}

	private void calcRate(long passedTime, Map<String, Integer> nowMap,
			Map<String, Integer> oldMap, Map<String, Integer> rateMap) 
	{
		for(String key : nowMap.keySet())
		{
			Integer nowStat = nowMap.get(key);					
			Integer oldStat = oldMap.get(key);
			
			if(oldStat == null)
			{
				oldStat = 0;
			}
			
			long rate = (long)(1000.f*(float)(nowStat - oldStat)/(float)passedTime);
			
			rateMap.put(key, key.equals("connections")?nowStat:(int)rate);
		}
	}
	
	public static class CalcTask extends TimerTask 
	{
		ModuleServer server;
		long t0 = -1;
		
		public CalcTask(ModuleServer server) {
			super();
			this.server = server;
		}

		public void run()
		{
			if(t0 == -1)
			{
				t0 = System.currentTimeMillis();
				return;
			}
			
			server.calcStat(System.currentTimeMillis() - t0);
			t0 = System.currentTimeMillis();
			server.formatStatString();
		}
	}

	/**
	 * 傳送 shutdown 的命令給指定的 module
	 * 
	 * @param moduleId
	 */
	public static void sendShutdownModule(String moduleId) {
		GroupMessage msg = new GroupMessage();
		msg.setMessageType(MessageTypes.S_MODULE_SHUTDOWN_MODULE);
		msg.setMessageContent(moduleId);
		msg.setDestModuleID(moduleId);

		MessageAction.sendGroupMessage(msg);
	}

	/**
	 * 傳送 shutdown 的命令給指定的 module
	 * 
	 * @param moduleId
	 */
	public static void sendRestartModule(String moduleId) {
		GroupMessage msg = new GroupMessage();
		msg.setMessageType(MessageTypes.S_MODULE_RESTART_MODULE);
		msg.setMessageContent(moduleId);
		msg.setDestModuleID(moduleId);

		MessageAction.sendGroupMessage(msg);
	}

	@SuppressWarnings("unchecked")
	public static ArrayList<ModuleStatus> sendGetAllModuleStatus() {
		ArrayList<ModuleStatus> moduleStatusList = new ArrayList<ModuleStatus>();
		TransportManager tm = ServerGroup.getTransportManager();

		ArrayList<String> vmModuleList = new ArrayList<String>(tm.getOneModuleInAllAddress());

		for (String vmModuleId : vmModuleList) {
			ArrayList<ModuleStatus> tempModuleStatusList;

			if (tm.isLocalModule(vmModuleId)) {
				// 如果是在本地就直接要
				tempModuleStatusList = tm.getLocalModuleStatusList();
			} else {
				// 在不同 vm 就用 group message 去要
				GroupMessage msg = new GroupMessage();
				msg.setMessageType(MessageTypes.S_MODULE_GET_ALL_MODULE_LIST);
				msg.setDestModuleID(vmModuleId);
				msg.setSrcService(Contexts.getModule().getModuleName());

				tempModuleStatusList = (ArrayList<ModuleStatus>) MessageAction.sendMessageWaitResponse(msg);
			}

			moduleStatusList.addAll(tempModuleStatusList);
		}

		return moduleStatusList;
	}

	/**
	 * 關閉指定的 module
	 * 
	 * @param moduleId
	 */
	public void shutdownModule(String moduleId) {
		BasicServiceModule module = getBasicServiceModule(moduleId);
		if (module == null) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + ": get module " + moduleId + " fail.");
			}
			return;
		}

		module.shutdown();
		removeModule(module);

		if (log.isInfoEnabled()) {
			log.info(GameUtility.getCurMethodName() + " " + moduleId + " finish.");
		}
	}

	/**
	 * 關閉指定的 module
	 * 
	 * @param moduleId
	 */
	public void restartModule(String moduleId) {
		BasicServiceModule module = getBasicServiceModule(moduleId);
		if (module != null) {
			if (log.isErrorEnabled()) {
				log.error(GameUtility.getCurMethodName() + ": fail, module " + moduleId + " already exist.");
			}
			return;
		}

		module = createModule(moduleId);
		module.init();
		module.startup();
		module.onModuleServerReadyStart();
		module.onServerConnectReady();

		addModule(module);

		if (log.isInfoEnabled()) {
			log.info(GameUtility.getCurMethodName() + " " + moduleId + " finish.");
		}
	}

	//	public int getPlayers()
//	{
//		int playerSize = 0;
//		for(BasicServiceModule module : allModules)
//		{
//			if(module instanceof BasicZoneModule)
//			{
//				playerSize += ((BasicZoneModule) module).getPlayers();
//			}
//		}
//		
//		return playerSize;
//	}
//	
//	private void registerJmxInfo()
//	{
//		MBeanServer  mbserver = ManagementFactory.getPlatformMBeanServer();
//		ModulerServerStat bean = new ModulerServerStat(this);
//		 
//		try 
//		{
//			ObjectName name = new ObjectName("Server:name=ModuleServer - " + this.getName());
//			mbserver.registerMBean(bean, name);
//		} 
//		catch (Exception e)
//		{
//			log.error(e, e);
//		}		
//	}
}
